Applications can perform their prescribed tasks using a collection of code components. In the illustrative case of object-oriented programming, for example, applications can allocate, in their source code, different classes that can be used at runtime to provide functionality for performing defined tasks. Namely, at runtime, a computing machine can instantiate these classes to provide specific objects (also referred to as “instances”). These objects are stored in an allocated portion of the computing machine's memory. After these objects have served their respective purposes and are no longer needed, the computing machine can run so-called garbage collection functionality to remove these objects from memory, thus providing room for the creation and storage of additional objects.
At any given time in the application's runtime performance, the computing machine's memory can be expected to store a large collection of runtime code components. These code components must interact with each other to perform their prescribed tasks. To this end, an application will provide functionality for “wiring” these code components together. The wiring allows components to refer to other components, such that the components can exchange information among themselves to perform their allotted tasks.
An application will typically apply some protocol in creating, managing, and severing such wiring links between components. Externally, such a protocol results in a logical flow of user interface presentations for consumption by an end-user. Internally, however, the protocol may generate a complex mesh of component interdependencies that is essentially unstructured.
A number of difficulties stem from traditional techniques for managing instances of code components. For instance, a code developer may have great difficulty in understanding the internal runtime behavior of an application. This can prevent the developer from proactively testing the application to discover potential software errors, and can also prevent the developer from successfully uncovering the source of errors that have been detected. Generally, software errors that manifest themselves at runtime (as opposed to, for example, compile-time) often have a latent nature, occurring only when certain conditions are present during the execution of the application. In the traditional approach, it may thus be difficult to uncover these errors before a program is shipped to a customer. In other cases, the application may provide its services in an error-free manner, yet because of various problems, it may do so in a suboptimal manner (that is, by using more memory than should be required, or taking more time than should be required).
One particularly vexing source of errors stems from traditional garbage collection techniques. These traditional techniques determine whether an object is no longer needed by keeping track of the reference links that point to the object. When the number of links reaches zero, the garbage collection functionality disposes of the object. This technique can impose complex accounting demands, as it must maintain accurate counts of many reference links. Due to this complexity, this accounting task is often a source of error. A common error results when an object no longer has any use, yet an instance still maintains a reference link to the object. This has the effect of keeping the object alive when it should be removed, thereby creating a kind of “zombie” instance. Needless to say, this kind of error can result in various problems, ranging from poor memory utilization (e.g., “resource leaks”) to program crashes. Traditional strategies are not well equipped to resolve these kinds of problems. Namely, because these strategies rely on an unstructured mesh of instances at runtime that may be poorly understood by the code developer, it becomes difficult to detect the occurrences of such “zombie” instances that may be collecting within the mesh.
Further, the use of traditional methods prevents a developer from writing code that efficiently provides certain functionality to groups of components at runtime. For instance, a developer may attempt to provide certain functionality to various components by “wiring” instances of this functionality to the components that need this functionality using various paradigms. However, atomistic coupling of services to individual components is costly, as it requires the storage of reference information which describes these separate links. Further, as appreciated by the present inventors, known approaches fail to provide suitable mechanisms for allowing components that rely on the same functionality, and therefore share a common “context,” to access such functionality in an efficient manner. Conventional techniques for sharing context (such as by using global variables) provide sharing on a relatively coarse-grained basis (e.g., on a process or thread level), and are therefore inadequate solutions to this problem.
For at least these illustrative and exemplary reasons, there is a need for more efficient strategies for managing code components at runtime that eliminate or reduce the occurrence of one or more of the problems identified above.